package org.ifi.p17.ccolor;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;

public class ColorDectector {
	private Mat mHSV;
    private Scalar mColorRadius = new Scalar(25, 50, 50, 0);
    private Scalar mLowerBound = new Scalar(0);
    private Scalar mUpperBound = new Scalar(0);
    private double mMinContourArea;
    private Mat mSpectrum = new Mat();
    private List<MatOfPoint> mContours = new ArrayList<MatOfPoint>();
    
    // Cache
    Mat mPyrDownMat = new Mat();
    Mat mHsvMat = new Mat();
    Mat mMask = new Mat();
    Mat mDilatedMask = new Mat();
    Mat mHierarchy = new Mat();

    public void process(Mat rgbaImage) {
        Imgproc.pyrDown(rgbaImage, mPyrDownMat);
        Imgproc.pyrDown(mPyrDownMat, mPyrDownMat);

        Imgproc.cvtColor(mPyrDownMat, mHsvMat, Imgproc.COLOR_RGB2HSV_FULL);

        Core.inRange(mHsvMat, mLowerBound, mUpperBound, mMask);
        Imgproc.dilate(mMask, mDilatedMask, new Mat());

        List<MatOfPoint> contours = new ArrayList<MatOfPoint>();

        Imgproc.findContours(mDilatedMask, contours, mHierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

        // Find max contour area
        double maxArea = 0;
        Iterator<MatOfPoint> each = contours.iterator();
        while (each.hasNext()) {
            MatOfPoint wrapper = each.next();
            double area = Imgproc.contourArea(wrapper);
            if (area > maxArea)
                maxArea = area;
        }

        // Filter contours by area and resize to fit the original image size
        mContours.clear();
        each = contours.iterator();
        while (each.hasNext()) {
            MatOfPoint contour = each.next();
            if (Imgproc.contourArea(contour) > mMinContourArea*maxArea) {
                Core.multiply(contour, new Scalar(4,4), contour);
                mContours.add(contour);                
            }
        }
    }
    public Mat replaceColor(Mat imageRgb){
        Imgproc.pyrDown(imageRgb, mPyrDownMat);
        //Imgproc.pyrDown(mPyrDownMat, mPyrDownMat);

        Imgproc.cvtColor(imageRgb, mHsvMat, Imgproc.COLOR_RGB2HSV_FULL);

        Core.inRange(mHsvMat, mLowerBound, mUpperBound, mMask);
        Imgproc.dilate(mMask, mDilatedMask, new Mat());
                
        List<Mat> hsvChanels = new ArrayList<Mat>();
        
        Core.split(mHsvMat, hsvChanels);      
        Mat imageHS = new Mat();
        
        Core.merge(hsvChanels.subList(0, 2), imageHS);
        imageHS.setTo(new Scalar(45,255,0), mDilatedMask);
        
        List<Mat> hsvImageNewColors = new ArrayList<Mat>();
        Core.split(imageHS, hsvImageNewColors);
        
        //Mat dest = new Mat(imageRgb.size(), imageRgb.type());
        Mat dest = new Mat();
        hsvImageNewColors.add(2, hsvChanels.get(2));
        //hsvImageNewColors.add(3,null);
        
        Core.merge(hsvImageNewColors, dest);
        Imgproc.cvtColor(dest, dest, Imgproc.COLOR_HSV2RGB_FULL);
        return dest;
    }
    
    public void setColorRadius(Scalar radius) {
        mColorRadius = radius;
    }
    
    public void setColorHsv(Scalar colorHsv){
        double minH = (colorHsv.val[0] >= mColorRadius.val[0]) ? colorHsv.val[0]-mColorRadius.val[0] : 0;
        double maxH = (colorHsv.val[0]+mColorRadius.val[0] <= 255) ? colorHsv.val[0]+mColorRadius.val[0] : 255;

        mLowerBound.val[0] = minH;
        mUpperBound.val[0] = maxH;

        mLowerBound.val[1] = colorHsv.val[1] - mColorRadius.val[1];
        mUpperBound.val[1] = colorHsv.val[1] + mColorRadius.val[1];

        mLowerBound.val[2] = colorHsv.val[2] - mColorRadius.val[2];
        mUpperBound.val[2] = colorHsv.val[2] + mColorRadius.val[2];

        mLowerBound.val[3] = 0;
        mUpperBound.val[3] = 255;

        Mat spectrumHsv = new Mat(1, (int)(maxH-minH), CvType.CV_8UC3);

        for (int j = 0; j < maxH-minH; j++) {
            byte[] tmp = {(byte)(minH+j), (byte)255, (byte)255};
            spectrumHsv.put(0, j, tmp);
        }

        Imgproc.cvtColor(spectrumHsv, mSpectrum, Imgproc.COLOR_HSV2RGB_FULL, 4);   	
    }
    
    public Mat getSpectrum() {
        return mSpectrum;
    }

    public void setMinContourArea(double area) {
        mMinContourArea = area;
    }
    public List<MatOfPoint> getContours() {
        return mContours;
    }
}
